﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace RepositoryFramework
{
	/// <summary>
	/// Class <see cref="GenericRepository"/> define generic repository
	/// </summary>
	/// <typeparam name="TContext">The type of the context.</typeparam>
	/// <typeparam name="TEntity">The type of the entity.</typeparam>
	public abstract class GenericRepository<TContext, TEntity>
		where TEntity : class
		where TContext : DbContext
	{
		#region Fields

		private readonly DbSet<TEntity> _dbSet;
		private readonly TContext _entities;
		private bool _disposed = false;

		#endregion Fields

		#region Properties

		/// <summary>
		/// Returns entities
		/// </summary>
		protected TContext Entities
		{
			get { return _entities; }
		}

		#endregion Properties

		#region .ctor

		/// <summary>
		/// Class <see cref="GenericRepository"/> constructor
		/// </summary>
		/// <param name="entities"></param>
		public GenericRepository(TContext entities)
		{
			if (entities == null)
			{
				throw new ArgumentNullException("entities");
			}
			_entities = entities;
			_dbSet = entities.Set<TEntity>();
		}

		#endregion .ctor

		#region Methods

		/// <summary>
		/// Counts TEntity instances.
		/// </summary>
		/// <returns></returns>
		public int Count()
		{
			return _dbSet.Count();
		}

		/// <summary>
		/// Counts TEntity instances.
		/// </summary>
		/// <returns></returns>
		public async Task<int> CountAsync()
		{
			return await _dbSet.CountAsync();
		}

		/// <summary>
		/// Delete entity by id
		/// </summary>
		/// <param name="id"></param>
		public virtual void Delete(object id)
		{
			TEntity entityToDelete = _dbSet.Find(id);
			Delete(entityToDelete);
		}

		/// <summary>
		/// Delete entity by id
		/// </summary>
		/// <param name="entity"></param>
		public virtual void Delete(TEntity entity)
		{
			if (_entities.Entry(entity).State == EntityState.Detached)
			{
				_dbSet.Attach(entity);
			}
			_dbSet.Remove(entity);
		}

		//public void Delete(TObject t)
		//{
		//	_dbSet.Remove(t);
		//	_context.SaveChanges();
		//}

		//public async Task<int> DeleteAsync(TObject t)
		//{
		//	_dbSet.Remove(t);
		//	return await _context.SaveChangesAsync();
		//}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		//public TObject Find(Expression<Func<TObject, bool>> match)
		//{
		//	return _dbSet.SingleOrDefault(match);
		//}

		//public ICollection<TObject> FindAll(Expression<Func<TObject, bool>> match)
		//{
		//	return _dbSet.Where(match).ToList();
		//}

		//public async Task<ICollection<TObject>> FindAllAsync(Expression<Func<TObject, bool>> match)
		//{
		//	return await _dbSet.Where(match).ToListAsync();
		//}

		//public async Task<TObject> FindAsync(Expression<Func<TObject, bool>> match)
		//{
		//	return await _dbSet.SingleOrDefaultAsync(match);
		//}

		public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
		{
			IQueryable<TEntity> query = _dbSet;

			if (filter != null)
			{
				query = query.Where(filter);
			}

			foreach (var includeProperty in includeProperties.Split
				(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
			{
				query = query.Include(includeProperty);
			}

			if (orderBy != null)
			{
				return orderBy(query).ToList();
			}
			else
			{
				return query.ToList();
			}
		}

		//public TObject Get(int id)
		//{
		//	return _dbSet.Find(id);
		//}

		//public ICollection<TObject> GetAll()
		//{
		//	return _dbSet.ToList();
		//}

		//public async Task<ICollection<TObject>> GetAllAsync()
		//{
		//	return await _dbSet.ToListAsync();
		//}

		//public async Task<TObject> GetAsync(int id)
		//{
		//	return await _dbSet.FindAsync(id);
		//}

		/// <summary>
		/// Get entity by Id
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public virtual TEntity GetByID(object id)
		{
			return _dbSet.Find(id);
		}

		//public TObject Insert(TObject t)
		//{
		//	_dbSet.Add(t);
		//	_context.SaveChanges();
		//	return t;
		//}

		/// <summary>
		/// Insert entity
		/// </summary>
		/// <param name="entity"></param>
		public virtual void Insert(TEntity entity)
		{
			_dbSet.Add(entity);
		}

		//public async Task<TObject> InsertAsync(TObject t)
		//{
		//	_dbSet.Add(t);
		//	await _context.SaveChangesAsync();
		//	return t;
		//}
		//public TObject Update(TObject updated, int key)
		//{
		//	if (updated == null)
		//		return null;

		//	TObject existing = _dbSet.Find(key);
		//	if (existing != null)
		//	{
		//		_context.Entry(existing).CurrentValues.SetValues(updated);
		//		_context.SaveChanges();
		//	}
		//	return existing;
		//}

		/// <summary>
		/// Update entity
		/// </summary>
		/// <param name="entity"></param>
		public virtual void Update(TEntity entity)
		{
			_dbSet.Attach(entity);
			_entities.Entry(entity).State = EntityState.Modified;
		}

		//public async Task<TObject> UpdateAsync(TObject updated, int key)
		//{
		//	if (updated == null)
		//		return null;

		//	TObject existing = await _dbSet.FindAsync(key);
		//	if (existing != null)
		//	{
		//		_context.Entry(existing).CurrentValues.SetValues(updated);
		//		await _context.SaveChangesAsync();
		//	}
		//	return existing;
		//}

		protected virtual void Dispose(bool disposing)
		{
			if (!this._disposed)
			{
				if (disposing)
				{
					_entities.Dispose();
				}
			}
			this._disposed = true;
		}

		#endregion Methods
	}
}